
function TRailBot(options){
  if(typeof(options)!='object'){
    options=new Object();
  }
//Scripts
  this.rails={0:[]};

//Data storage   
  if(typeof(TStorage)=='function'){
    this.vars=new TStorage();
  }
  this.vars.set['currentPosition'] = {
    rail_id:'main',
  	rail_line:-1
  };
  this.vars.set['maxPageLines'] = 100;
  
  this.vars.load();

//log
  if(typeof(TLog)=='function'){
    this.log=new TLog();
  }else{
    this.log={s:function(str){alert(str)}};
  }

	 
//---
  this.jump_line = function(line){
    var posInfo = this.vars.sv('currentPosition');
		posInfo.rail_line=line-1;
		this.vars.sv('currentPosition',posInfo);
	    return 0;
  }
  this.change_rail = function(railid,line){
    line=line||0;
    var posInfo = this.vars.sv('currentPosition');
    posInfo.rail_id=railid;
    posInfo.rail_line=line-1;
	this.vars.sv('currentPosition',posInfo);
	return 0;
  }
	
	this.stack_ret = function(){
     var script_stack = this.vars.sv('script_stack');
     if(script_stack.length>0){
       var dest = script_stack.pop();
       this.vars.sv('script_stack',script_stack);
       this.change_rail(dest['ret_rail'],dest['ret_line']);
     }
  }
	
  this.reset = function(){
    this.vars.sv('script_stack',new Array());
    this.vars.gv('continue_script',false);
    this.jump_line(0);
  }

  this.navigate = function(params){
	    this.vars.gv('continue_script',true);
	    GM_log('navigating: '+params.url);
	  	document.location.href=params.url;
  }
  
  this.run = function(script_id){
    if(this.vars.gv('continue_script')==false){
      this.change_rail(script_id,0);
    }  
    this.nextLine();
  }
  
//page analyzing module

  this.pg = new TPageAnalyzing({
    storage:this.vars,
    log:this.log
  });
  this.pg.pageIdSigns = options.pageIdSigns || new Object();

  this.nextLine = function(){
    //alert(uneval(this))
    var posInfo = this.vars.sv('currentPosition');
    if(typeof(this.rails[posInfo.rail_id])=='object'){
	  posInfo.rail_line++;
	  this.vars.sv('currentPosition',posInfo);
	  if(this.rails[posInfo.rail_id].length <= posInfo.rail_line){
	    posInfo.rail_line=0;	    
  	  this.vars.sv('currentPosition',posInfo);
	  }
	  if(this.rails[posInfo.rail_id][posInfo.rail_line]){
	    var line = this.rails[posInfo.rail_id][posInfo.rail_line];
	    if(typeof(line)=='object'){
        line['current_rail']=posInfo.rail_id;
        line['current_line']=posInfo.rail_line;
      }  
	    return this.handleLine(line);
	  }else{
  	    this.log.s('wrong line, probably empty script: '+uneval(posInfo));
	  }
	  
	}else{
	  this.log.s('wrong rail: '+uneval(posInfo));
	  //alert(uneval(this.rails))
	  
	}
  }


  this._handlers = new Object();
   this._handlers._default = function(rb_parent,line){
	  rb_parent.log.s('WARNING: no handler found, line: '+line);
	  return 1;
	}
    this._handlers._types = {
	  'boolean':function(val){},
	  'number':function(val){},
	  'string':function(val){}
	}
	this._handlers._arrays = {
	  _default:function(){},
	  _1:function(){},
	  _2:function(){}
	}
	this._handlers._functions = new Object();
	  this._handlers._functions.delay = function(rb_parent,delayms){
	    delayms=delayms||5;
        window.setTimeout(function(){
    	   rb_parent.nextLine();
		  }
		,delayms);
	    return 'async';
	  }
	  this._handlers._functions.exec = function(rb_parent,func){
	    func();
	    return 0;
	  }
	  this._handlers._functions.alert = function(rb_parent,msg){
	    alert(msg);
  		return 0;
	  }
	  this._handlers._functions.log = function(rb_parent,msg){
	    rb_parent.log.s(msg);
  		return 0;
	  }
	  
	  
	  this._handlers._functions.exit = function(rb_parent){
	    //rb_parent.log.s('Stopped: '+uneval(rb_parent.vars));
	    return 'async';
	  }
	  this._handlers._functions.chgrail = function(rb_parent,params){
	    rb_parent.change_rail(params['rail'],(params['line']||0));
	    return 0;
	  }
	  this._handlers._functions.call_script = function(rb_parent,params){
	    var script_stack = rb_parent.vars.sv('script_stack')|| new Array();
	    var posInfo = rb_parent.vars.sv('currentPosition');
	      script_stack.push({
	        ret_rail:posInfo['rail_id'],
	        ret_line:posInfo['rail_line']+1
        });
        rb_parent.vars.sv('script_stack',script_stack);
        rb_parent.change_rail(params['rail'],params['line']||0);
	    return 0;
	  }
	  this._handlers._functions.ret = function(rb_parent,params){
	    rb_parent.stack_ret();
	    return 0;
	  }
	  
	  
	  this._handlers._functions.jump = function(rb_parent,lineid){
	    rb_parent.jump_line(lineid);
	    return 0;
	  }
	  this._handlers._functions.goto = function(rb_parent,label){
	    var posInfo = rb_parent.vars.sv('currentPosition');
	    var currRail = rb_parent.rails[posInfo['rail_id']];
	    for(var q in currRail){
	      if(typeof(currRail[q])=='object' && typeof(currRail[q]['label'])=='string'){
	        rb_parent.jump_line(q);
        }
      }
	    return 0;
	  }
	  this._handlers._functions.reset = function(rb_parent){
	    rb_parent.jump_line(0);
	    return 0;
	  }
	  this._handlers._functions.navigate = function(rb_parent,params){
  	  rb_parent.navigate(params);
	    return 'async';
	  }
	  
  this.addHandlerFunc = function(func_name,func){
    this._handlers._functions[func_name]=func;
  }	  
	  
	  
  this.handleLine = function(line){
    var linetype = typeof(line);
	var handlerRet = 0;
    switch(linetype){
	  case 'object':{
	    if(typeof(line['label'])=='string'){
	      handlerRet=0;
      }
	  
	    if(typeof(line['call'])=='string'){
		  if(this._handlers._functions[line['call']]){
		    var func=this._handlers._functions[line['call']];
		    var funcParams=[];
		    if(line.params){
		      funcParams=line.params;
			}
			handlerRet = func(this,funcParams);
		  }
		  
		}
	    break;
	  }
	  case 'boolean':
	  case 'number':
	  case 'string':{
	    if(this._handlers._types[linetype]){
		  handlerRet = (this._handlers._types[linetype])(this,line);
		}else{
		  handlerRet = (this._handlers._default)(this,line);
		}
	  }
	} 
	//alert(handlerRet);
	if(handlerRet=='async'){
	}else{
	  this.nextLine();
	}
  }
  
  
  //INIT
  if(!this.vars.gv('continue_script')){
    this.reset();
    this.vars.sv('script_stack',new Array());
  }
  
  
}

function TPageAnalyzing(options){
  if(typeof(options.storage)=='object'){
    this.vars=options.storage;
  }else{
    this.vars=TStorage();
  }
  if(typeof(options.log)=='object'){
    this.log=options.log;
  }else{
    this.log=TLog();
  }
  
  this.loc = document.location.href || window.location.href;
  this.ids = [];

  this.pgid = function(pid){
    for(var q in this.ids){
      if(this.ids[q]==pid){
        return true;
      }
    }
    return false;
  }

//Ð¼Ð°ÑÑÐ¸Ð² Ñ Ð¸Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ‚Ð¾Ñ€Ð°Ð¼Ð¸ ÑÑ‚Ñ€Ð°Ð½Ð¸Ñ† Ð¸ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð², Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÑÑŽÑ‰Ð¸Ñ… ÑÑ‚Ð¸ id  
//Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ ÑÑ‚Ñ€Ð°Ð½Ð¸Ñ†Ñ‹ Ð¼Ð¾Ð³ÑƒÑ‚ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÑÑ‚ÑŒÑÑ Ð½ÐµÑÐºÐ¾Ð»ÑŒÐºÐ¾ id
/*
  locm:            loc.match
  htmlm:           hmtl match
  xp_exists:       elem by xpath, TRUE if exists
  xp_htmlm:        {xp,str} : elem by xpath, TRUE if elem.innerHTML.match
  func:            user defined function
SAMPLE:
  {
    'login':{locm:"login.php"},
    'settings':{htmlm:/Apply settings/gi},
  	'search':{xp_exists:"//input[@id='search']"}
  	'custom':{func:function(){return true}}
  }

*/
  this.pageIdSigns = new Object();
  this.pageIdHandlers = new Object();
    
  this._checkSign = function(sign){
    if(sign.locm){
	  if(this.loc.match(sign.locm)){
	    return true;
	  }else{
	    return false;
	  }
	}
  if(sign.htmlm){
	  //TODO
	}
  if(sign.xp_exists){
	  var xel = xp(sign.xp_exists);
	  if(xel){
	    return true;
	  }else{
	    return false;
	  }
	}
  if(sign.xp_htmlm){
	  var xel = xp(sign.xp_htmlm);
	  if(xel){
  	    if(xel.innerHTML.match(sign.locm)){
	      return true;
	    }else{
	      return false;
	    }
	  }else{
	    return false;
	  }
	}
  if(sign.func){
    if(typeof(sign.func)=='function'){
      return sign.func();
    }
    return false;
	}

  }
  this._getPageIds = function(){
    var retArr=new Array();
    for(var q in this.pageIdSigns){
	    if(this._checkSign(this.pageIdSigns[q])){
	      retArr.push(q);	    
	    }
	  }
	  if(retArr.length==0){ 
	    retArr.push('none');
    }
	  return retArr;
  }	
  
  this._handleIds = function(){
    for(var q in this.ids){
      if(typeof(this.pageIdHandlers[this.ids[q]])=='function'){
        this.pageIdHandlers[this.ids[q]]();
      }
    }  
  }

  this.analyze = function(){
    this.ids = this._getPageIds();
    this._handleIds();
  }
  
  this.INIT = function(){
  }
  
  this.INIT();
}

function TLog(options){
  this.settings={
    enableAlerts:true,
  	alert_type:'alert' //alert,softalert,panel, ...
  }
  this.alert = function(str){
    //TODO:
    alert(str);
  }
  this.log_str = function(str){
    if(typeof(GM_log)=='function'){
	  GM_log(str);
	  return 0;
	}
	if(this.settings.enableAlerts){
  	  alert(str);
	}
  }
  this.s = function(str){
    var logFunc = this.log_str;
	  logFunc(str);
  }
}

function TTimer(options){
  
}

function TStorage(){
  this.global=new Object();
  this.page=new Object();
  this.set=new Object();
  
  this.saveVar = function(vname,val){
    if(typeof(GM_setValue)=='function'){
      GM_setValue(vname,val);
	}
  }
  this.loadVar = function(vname,def){
    if(typeof(GM_getValue)=='function'){
      return GM_getValue(vname,def);
	}  
  }
  this.save = function(){
    this.saveVar('vars.global',uneval(this.global));
    this.saveVar('vars.set',uneval(this.set));
  }
  this.load = function(){
    this.global = eval(this.loadVar('vars.global',this.global));
    this.set = eval(this.loadVar('vars.set',this.set));
  }
  this.gv = function(pname,pval){
    if(typeof(pval)!='undefined'){ 
	  this.global[pname]=pval;
	  this.save();
	  return 1;
	}
	return this.global[pname];
  }
  this.pv = function(pname,pval){
    if(typeof(pval)!='undefined'){ 
	  this.page[pname]=pval;
	  return 1;
	}
	return this.page[pname];
  }
  this.sv = function(pname,pval){
    if(typeof(pval)!='undefined'){ 
	  this.set[pname]=pval;
	  this.save();
	  return 1;
	}
	return this.set[pname];
  }
}

function xp(path){
   var x = document.evaluate(
   path,
   document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
   return x.snapshotItem(0);
}

function xps(path){
   var res = new Array();
   var x = document.evaluate(
   path,
   document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
   for(var q=0;q<x.snapshotLength;q++){
      res[res.length]=x.snapshotItem(q);
   }
   if(x.snapshotLength==0){
      return eval('[]');
   }
   return res;
}